package com.zzyl.nursing.service.impl;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSON;
import com.zzyl.common.constant.CacheConstants;
import com.zzyl.common.exception.base.BaseException;
import com.zzyl.common.utils.DateUtils;
import com.zzyl.nursing.domain.AlertData;
import com.zzyl.nursing.domain.DeviceData;
import com.zzyl.nursing.mapper.DeviceMapper;
import com.zzyl.nursing.service.IAlertDataService;
import com.zzyl.system.mapper.SysUserRoleMapper;
import org.apache.poi.ss.formula.functions.Count;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import com.zzyl.nursing.mapper.AlertRuleMapper;
import com.zzyl.nursing.domain.AlertRule;
import com.zzyl.nursing.service.IAlertRuleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * 报警规则Service业务层处理
 * 
 * @author zzyl
 * @date 2025-10-28
 */
@Service
public class AlertRuleServiceImpl extends ServiceImpl<AlertRuleMapper, AlertRule> implements IAlertRuleService
{
    @Autowired
    private AlertRuleMapper alertRuleMapper;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private DeviceMapper deviceMapper;
    @Autowired
    private SysUserRoleMapper sysUserRoleMapper;
    @Value("${alertRule.roleAdmin}")
    private String roleAdmin;
    @Value("${alertRule.roleRepair}")
    private String roleRepair;
    @Autowired
    private IAlertDataService alertDataService;
    /**
     * 查询报警规则
     * 
     * @param id 报警规则主键
     * @return 报警规则
     */
    @Override
    public AlertRule selectAlertRuleById(Long id)
    {
        return getById(id);
    }

    /**
     * 查询报警规则列表
     * 
     * @param alertRule 报警规则
     * @return 报警规则
     */
    @Override
    public List<AlertRule> selectAlertRuleList(AlertRule alertRule)
    {
        return alertRuleMapper.selectAlertRuleList(alertRule);
    }

    /**
     * 新增报警规则
     * 
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int insertAlertRule(AlertRule alertRule)
    {
        return save(alertRule) ? 1 : 0;
    }

    /**
     * 修改报警规则
     * 
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int updateAlertRule(AlertRule alertRule)
    {
        return updateById(alertRule) ? 1 : 0;
    }

    /**
     * 批量删除报警规则
     * 
     * @param ids 需要删除的报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleByIds(Long[] ids)
    {
        return removeByIds(Arrays.asList(ids)) ? 1 : 0;
    }

    /**
     * 删除报警规则信息
     * 
     * @param id 报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleById(Long id)
    {
        return removeById(id) ? 1 : 0;
    }
    /**
     * 定时进行设备数据对应的报警规则校验，将报警数据保存到对应表中
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void alertRuleFilter() {
        //1.查询所有的规则
        List<AlertRule> alertRules = this.lambdaQuery().eq(AlertRule::getStatus, 1).list();
        if (CollUtil.isEmpty(alertRules)) {
            log.error("未查到任何正在启用中的报警规则");
            return;
        }
        //2.查询所有设备上报的数据
        List<Object> values = stringRedisTemplate.opsForHash().values(CacheConstants.DEVICE_DATA_NEW);
        if (CollUtil.isEmpty(values)) {
            log.error("未查询到任何设备上传的数据");
            return;
        }
        //3.数据格式标准化处理
        List<DeviceData> deviceDataList = new ArrayList<>();
        values.forEach(value -> {
            List<DeviceData> deviceData = JSONUtil.toList((String) value, DeviceData.class);
            deviceDataList.addAll(deviceData);
        });
        //4. 获取到全部的设备数据，进行过滤处理
        //4.1遍历所有设备的上报数据
        for (DeviceData deviceData : deviceDataList) {
            //10分钟
            int limit=3600;
            long between = LocalDateTimeUtil.between(deviceData.getAlarmTime(), LocalDateTime.now(), ChronoUnit.SECONDS);
            if (between > limit) {
                log.error("[定时报警规则校验]过十分钟的设备上报数据不再处理");
                continue;
            }
            //4.2准备对应报警规则数据
            //4.2.0报警规则绑定的设备有两种:1.绑定全部的设备 2.绑定指定的设备
            //4.2.1绑定的全部设备
            List<AlertRule> alertRules1 = this.lambdaQuery()
                    //相同的产品类型
                    .eq(AlertRule::getProductKey, deviceData.getProductKey())
                    //监测的功能
                    .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                    //对应的设备id,因为绑定的全部的设备,iotid为-1
                    .eq(AlertRule::getIotId, -1)
                    //规则是否启用
                    .eq(AlertRule::getStatus, 1).list();
            //4.2.2绑定的指定设备
            List<AlertRule> alertRules2 = this.lambdaQuery()
                    //相同的产品类型
                    .eq(AlertRule::getProductKey, deviceData.getProductKey())
                    //监测的功能
                    .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                    //对应的设备id
                    .eq(AlertRule::getIotId, deviceData.getIotId())
                    //规则是否启用
                    .eq(AlertRule::getStatus, 1).list();
            //4.2.3合并两个规则集合并去重
            Collection<AlertRule> alertRuleAll = CollUtil.union(alertRules1, alertRules2);
            //4.2.4判断是否在生效时间
            for (AlertRule alertRule : alertRuleAll) {
                //获得规则的生效时间段,格式 00:00:00~23:59:59
                String period = alertRule.getAlertEffectivePeriod();
                //获得时间的节点
                String[] periodArr = period.split("~");
                LocalTime startTime = LocalTime.parse(periodArr[0]);
                LocalTime endTime = LocalTime.parse(periodArr[1]);
                //获取数据上报的时间
                LocalTime alarmTime = LocalTime.from(deviceData.getAlarmTime());
                //判断
                if (alarmTime.isBefore(startTime)|| alarmTime.isAfter(endTime)) {
                    log.error("设备的提交时间不在规则的生效时间内");
                    continue;
                }
                //4.2.5判断监测的数值是否达到报警规则限定范围
                //获得设备的数值
                String dataValue = deviceData.getDataValue();
                //获得规则的限定范围和比较符号
                Long value = alertRule.getValue();
                //只有两种比较符号,<和>=
                String operator = alertRule.getOperator();
                //x大于y，结果就是1； x等于y，结果就是0； x小于y，结果就是-1
                int compare = NumberUtil.compare(Double.parseDouble(dataValue), value);
                //判断是否达到规则生效的数值,根据运算符分两种情况
                if (    //生效要求dataValue<value
                        operator.equals("<")&&compare==-1||
                        //生效要求dataValue>=value
                        operator.equals(">=")&&compare>=0){
                    //达到了要求
                //4.2.6第三层校验：检查沉默周期是否存在，存在就直接结束
                    //设定redis中沉默周期的key
                    String silentKey = CharSequenceUtil.format(CacheConstants.IOT_ALERT_SILENT_PREFIX, deviceData.getIotId(), deviceData.getFunctionId(), alertRule.getId());
                    //根据key从redis中获得沉默周期的value
                    String silentVal = stringRedisTemplate.opsForValue().get(silentKey);
                    //判断生命周期是否存在,存在说明生命周期还未结束,还未结束就不予处理
                    if (StrUtil.isNotBlank(silentVal)) {
                        //存在
                        log.error("[定时报警规则校验]沉默周期还未结束，无需处理");
                        continue;
                    }else {
                        //不存在
                //4.2.7第四层校验：判断持续周期对应的次数是否达到（没达到则直接结束）
                        //获取key
                        String countKey = CharSequenceUtil.format(CacheConstants.IOT_ALERT_COUNT_PREFIX, deviceData.getIotId(), deviceData.getFunctionId(), alertRule.getId());
                        //令持续周期对应的次数自增
                        Long increment = stringRedisTemplate.opsForValue().increment(countKey);
                        if(increment<alertRule.getDuration()) {
                            log.error("[定时报警规则校验]持续周期次数还未达到，无需处理");
                            continue;
                        }
                        //校验通过,当前数据会触发报警,触发报警要加上沉默周期
                        //保存沉默周期的值到Redis，过期时间为沉默周期对应的秒数
                        stringRedisTemplate.opsForValue().setIfAbsent(silentKey,"123",alertRule.getAlertSilentPeriod()*60, TimeUnit.SECONDS);
                        //删除Redis中持续次数周期缓存数据
                        stringRedisTemplate.delete(countKey);
                        //先设置护理员id的集合,方便后面使用
                        List<Long> nursingIds= null;
                        //5.1判断异常数据是属于老人还是设备,0是老人异常数据,1是设备异常数据
                        //判断是否是老人的异常数据
                        if (alertRule.getAlertDataType()==0){
                            //5.1.1是老人异常数据后,再次判断是随身设备还是固定设备,0是随身设备,1是固定设备
                            //是随身设备,直接调用方法查护理员id
                            if (deviceData.getLocationType()==0){
                                nursingIds = deviceMapper.selectNuringIdsByMoveIotId(deviceData.getIotId());
                            }
                            //是固定设备,直接调用方法查护理员id
                            else{
                               nursingIds = deviceMapper.selectNuringIdsByStopIotId(deviceData.getIotId());
                            }
                            //是设备异常数据,直接查询修理工
                        }else{
                            nursingIds = sysUserRoleMapper.selectUserIdsByRoleName(roleRepair);
                        }
                        //查询超级管理员
                        List<Long> adminUserIds = sysUserRoleMapper.selectUserIdsByRoleName(roleAdmin);
                        //合并集合并去重
                        Collection<Long> nursingAll = CollUtil.union(nursingIds, adminUserIds);
                        //封装数据报警对象
                        AlertData alertData = new AlertData();
                        BeanUtil.copyProperties(alertRule, alertData);
                        alertData.setId(null);
                        //设备数据类型 0-老人异常数据 1-设备异常数据
                        alertData.setType(alertRule.getAlertDataType());
                        //规则id
                        alertData.setAlertRuleId(alertRule.getId());
                        //0-待处理  1-已处理
                        alertData.setStatus(0);
                        String reasonTemplate = "功能{}{}{}，持续了{}个周期才报警，沉默周期：{}";
                        String alertReason = CharSequenceUtil.format(reasonTemplate,
                                        alertRule.getFunctionId(),
                                        alertRule.getOperator(),
                                        alertRule.getValue(),
                                        alertRule.getDuration(),
                                        alertRule.getAlertSilentPeriod()
                                );
                        //报警原因
                        alertData.setAlertReason(alertReason);
                        //所有用户报警数据的集合
                        List<AlertData> alertDataList = new ArrayList<>();
                        for (Long userId : nursingAll) {
                                //封装用户报警数据，设置用户ID
                                AlertData userAlertData = new AlertData();
                                BeanUtil.copyProperties(alertData, userAlertData);
                                userAlertData.setUserId(userId);
                                userAlertData.setDataValue(dataValue);
                                alertDataList.add(userAlertData);
                            }
                                //批量保存所有用户报警数据
                                boolean result = alertDataService.saveBatch(alertDataList);
                                if(!result) {
                                   throw new BaseException("保存用户保存数据失败");
                                }
                            }
                    } else {
                        log.error("[定时报警规则校验]数据上报的值没有达到规则阈值，无需处理");
                        continue;
                    }

                }

            }

        }
}

